#pragma once
#include <vector>
#include <semaphore.h>
#include <pthread.h>

template <class T>
class RingQueue
{
public:
    RingQueue(size_t size)
    :_size(size)
    ,_ringQueue(_size)
    ,_p_step(0)
    ,_c_step(0)
    {
        //初始化生产线程的信号量
        sem_init(&_space_sem, 0, _size);
        //初始化消费线程的信号量
        sem_init(&_data_sem, 0, 0);
        //初始化生产线程的锁
        pthread_mutex_init(&_p_mutex, nullptr);
        //初始化消费线程的锁
        pthread_mutex_init(&_c_mutex, nullptr);
    }
    void Pop(T& out)
    {
        /*pthread_mutex_lock(&_c_mutex); 
        sem_wait(&_data_sem); */
        //如果先加锁再申请信号量那么所有消费线程申请信号量是串行的，一个线程申请完另一个线程在申请，
        //由于申请信号量是原子操作是安全的，先申请信号量再加锁，那消费线程申请信号量就是并发的，这样的写法代码效率更高
        //虽然一次只有一个消费线程从共享资源中那取数据，但只要共享资源还有数据，其他消费线程就可提前预定数据
        sem_wait(&_data_sem); //申请信号量，申请成功该函数返回继续后面的代码，申请失败被阻塞
        pthread_mutex_lock(&_c_mutex); //加锁
        //这种写法的环形队列与阻塞队列相比效率更高，原因阻塞队列中对共享资源是满是空的判断只能在临界区中访问
        //而环形队列通过信号量对共享资源是满是空的判断可以在临界区外，
        //所以阻塞队列中对共享资源状态的查看是串行，环形队列对共享资源状态的查看是并发的，效率更高。
        out = _ringQueue[_c_step];
        _c_step++;
        _c_step %= _size;
        //解锁
        pthread_mutex_unlock(&_c_mutex); 
        //归还信号量
        sem_post(&_space_sem);//原子操作
    }
    void Enqueue(T& in)
    {
        sem_wait(&_space_sem);
        pthread_mutex_lock(&_p_mutex);
        _ringQueue[_p_step] = in;
        _p_step++;
        _p_step %= _size; 
        //解锁
        pthread_mutex_unlock(&_p_mutex); 
        sem_post(&_data_sem);
    }
    ~RingQueue()
    {
        //释放信号量
        sem_destroy(&_space_sem);
        sem_destroy(&_data_sem);
        //释放锁
        pthread_mutex_destroy(&_p_mutex);
        pthread_mutex_destroy(&_c_mutex);
    }

private:
    size_t _size;//环形队列的空间大小
    std::vector<T> _ringQueue;
    size_t _p_step;//生产者放数据的位置
    size_t _c_step;//消费者读取数据的位置
    sem_t _space_sem;
    sem_t _data_sem;
    pthread_mutex_t _p_mutex;//生产线程的锁
    pthread_mutex_t _c_mutex;//消费线程的锁
};
